《Android 基础(四十四)》 View事件分发机制

1. 前言

View的事件分发机制,指的时当一个点击事件或者一个触摸事件发生时,Android系统如何讲这个事件进行处理,“分发“表示这里存在上下级关系,就如同部门经理分发任务一样,这就涉及到任务从何而来,分发到谁手上,是否继续分发或者返回上级,直到最终确定任务谁来处理,如何处理。

2. 小测试

写一个简单的测试Demo,看看运行过程

2.1 ChildView

ChildView继承AppCompatButton,然后添加一些log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ChildView extends android.support.v7.widget.AppCompatButton {
public ChildView(Context context) {
super(context);
}

public ChildView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public ChildView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
System.err.println("yidong -- ChildView onTouchEvent event = " + event.getAction());
return super.onTouchEvent(event);
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
System.err.println("yidong -- ChildView dispatchTouchEvent event = " + event.getAction());
return super.dispatchTouchEvent(event);
}

}

2.2 ParentLayout

ParentLayout继承LinearLayout,而LinearLayout时继承ViewGroup的添加一些log

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ParentLayout extends LinearLayout {
public ParentLayout(Context context) {
super(context);
}

public ParentLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}

public ParentLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
System.err.println("yidong -- ParentLayout onTouchEvent event = " + event.getAction());
return super.onTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
System.err.println("yidong -- ParentLayout onInterceptTouchEvent event = " + ev.getAction());
//return super.onInterceptTouchEvent(ev);
return false;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
System.err.println("yidong -- ParentLayout dispatchTouchEvent event = " + ev.getAction());
return super.dispatchTouchEvent(ev);
}
}

2.3 布局文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<cn.onlyloveyd.androideventdispatchdemo.ParentLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/parentLayout"
tools:context="cn.onlyloveyd.androideventdispatchdemo.MainActivity">

<cn.onlyloveyd.androideventdispatchdemo.ChildView
android:id="@+id/childview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="ChildView"/>

</cn.onlyloveyd.androideventdispatchdemo.ParentLayout>

2.4 MainActivity

测试一下事件的传递过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MainActivity extends AppCompatActivity {
private ParentLayout parentLayout;
private ChildView childView;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

parentLayout = (ParentLayout) findViewById(R.id.parentLayout);
childView = (ChildView) findViewById(R.id.childview);


parentLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
System.err.println("yidong -- ParentLayout onTouch event = " + motionEvent.getAction());
return false;
}
});

childView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
System.err.println("yidong -- ChildView onTouch event = " + motionEvent.getAction());
return false;
}
});

childView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.err.println("yidong -- ChildView onClick");
}
});

}
}

2.4.1 ParentLayout不拦截事件

ParentLayout的onInterceptTouchEvent方法直接return false;虽然使用super方法也是返回false,因为ViewGroup中就是直接返回的false。

1
2
3
4
5
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
System.err.println("yidong -- ParentLayout onInterceptTouchEvent event = " + ev.getAction());
return false;
}
  • 点击Button
    这里写图片描述
    解释:
    ACTION_DOWN事件由ParentLayout开始分发(暂时不考虑从Activity开始,只考虑View和ViewGroup),经过dispatchTouchEvent方法,首先判断是否拦截该事件,由于我们没有设置拦截,事件继续传递到ChildView,ChildView拿到事件之后,也是通过dispatchTouchEvent来进行分发,由于没有子View,开始执行设置的onTouch方法,由于我们onTouch返回的是false,表示没有消耗事件,继续交给ChildView的onTouchEvent处理,而ChildView继承的时AppCompatButton,在onTouchEvent中会消耗掉事件,这样后续的ACTION_UP事件才会继续被处理,最后会ACTION_DOWN和ACTION_UP一起形成onClick事件。这里假设在ChildView的onTouchEvent返回的时false,我们将会看到另外一种log。
    这里写图片描述
    解释:
    由于ChildView在onTouch和onTouchEvent中都没有消耗掉ACTION_DOWN事件,事件被返回给ParentLayout,而ParentLayout的onTouch和onTouchEvent中也都是返回的false,表示没有消耗事件,因此,这个事件一直都没有被处理。就好像领导给你一个任务,你竟然说你完成不了,后续的任务,你觉得会给你嘛?所以后面的ACTION_UP的影子都看不到。假如我们在ParentLayout的onTouch或者onTouchEvent中返回true。

若ParentLayout.onTouch返回true

这里写图片描述
可以看到对于ACTION_DOWN事件在ParentLayout.onTouch结束,没有执行ParentLayout.onTouchEvent方法。而ACTION_UP在没有执行拦截判断的情况下,直接把事件交给ParentLayout的onTouch处理。就好像一个项目中的同一个模块的第一个任务,你很好的完成来,那么第二个任务,自然就是你的。因为你有能力完成,并且两个任务也是同一个模块。

若ParentLayout.onTouchEvetn返回true

前提时ParentLayout.onTouch方法return false
这里写图片描述
结果和上面的差不多,只是终点换来一个位置而已。

  • 点击Button外空白区域
    这里写图片描述
    解释:
    由于ParentLayout的onTouch和onTouchEvent都是返回的false,ACTION_DOWN 事件并没有被消耗,所以后续的ACTION_UP事件自然是没有的,假如我们在onTouch或者onTouchEvent中return true。

若ParentLayout.onTouch返回true

这里写图片描述

若PatentLayout.onTouchEvent返回true

这里写图片描述
解释基本和上面的一毛一样,不做赘述。

2.4.2 ParentLayout拦截事件

1
2
3
4
5
6
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
System.err.println("yidong -- ParentLayout onInterceptTouchEvent event = " + ev.getAction());
//return super.onInterceptTouchEvent(ev);
return true;
}

结果可想而知,你的组长把活都自己做来,作为组员的你,自然时看不到任务的。运行结果和上面不拦截的情况下点击Button外部相同。具体的log要看ParentLayout的onTouch和onTouchEvent的执行结果。

2.5 总结

这里写图片描述

2.6 测试代码

AndroidEventDispatchDemo

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×